package edu.njit.hz278.core.web;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;

/**
 * A handler is the entity that actually serves a HTTP request. A handler has 3
 * fields:
 * <ol>
 * <li>Object: an instance of module;</li>
 * </li>Method: a method defined in the class of `Object'.</li>
 * <li>
 * ReturnType (inferred by @Method): unlike the terminology we used in java,
 * here we refer to ReturnType as one of the following types:
 * <ol>
 * <li>Page: rendered as a page. similar concept as JSP;</li>
 * <li>JSON;</li>
 * <li>JSONP: might be useful when performing cross-domain requests.</li>
 * </ol>
 * </li>
 */

public class Handler {

	private final Invoker invoker;

	/**
	 * Constructor.
	 * 
	 * @param clsobj
	 *            , instance of your module.
	 * @param method
	 *            , method that serves a HTTP request.
	 */
	public Handler(Object clsobj, Method method) {

		Class<?>[] paramTypes = method.getParameterTypes();
		int paramLen = paramTypes.length;
		String[] paramNames = new String[paramLen];
		boolean[] paramForceValid = new boolean[paramLen];
		boolean forceValid = false;
		boolean needsBindingResult = false;

		/**
		 * check if we have a BindingResult instance.
		 */

		for (int i = 0; i != paramLen; ++i)
			if (paramTypes[i] == BindingResult.class) {
				needsBindingResult = true;
				break;
			}

		/**
		 * check if it is forced to be valid
		 */
		Annotation[][] annoMatrix = method.getParameterAnnotations();
		outer: for (int i = 0, len1 = annoMatrix.length; i < len1; ++i) {
			for (int j = 0, len2 = annoMatrix[i].length; j < len2; ++j)
				if (annoMatrix[i][j] instanceof Param) {
					Param param = (Param) annoMatrix[i][j];
					// we only process the 1st annotation of Param
					paramNames[i] = param.value();
					paramForceValid[i] = param.forceValid();
					forceValid = forceValid || param.forceValid();
					continue outer;
				}
			log.warn(String
					.format("Argument %d of method %s doesn't associate with any parameter.",
							i, method.getName()));
		}

		/**
		 * determine the invoker we want
		 */

		if (needsBindingResult) {
			if (forceValid)
				invoker = new BindingResultAndValidityInvoker(clsobj, method,
						paramNames, paramForceValid);
			else
				invoker = new BindingResultInvoker(clsobj, method, paramNames);
		} else {
			if (forceValid)
				invoker = new ValidityInvoker(clsobj, method, paramNames,
						paramForceValid);
			else
				invoker = new NaiveInvoker(clsobj, method, paramNames);
		}

	}

	/**
	 * Invokes this handler. Typically, the arguments will be parsed
	 * automatically and no need for developers to take care of it themselves.
	 * However, I also provide developers a limited ability to manipulate
	 * {@link HttpServletRequest} and {@link HttpServletResponse}. Also, the
	 * resultant behavior indicated by ReturnType will be performed
	 * automatically.
	 * 
	 * @param req
	 * @param resp
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 * @throws JsonParseException
	 * @throws JsonMappingException
	 * @throws IOException
	 * @throws InstantiationException
	 */
	public void invoke(HttpServletRequest req, HttpServletResponse resp)
			throws IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, JsonParseException,
			JsonMappingException, IOException, InstantiationException {

		invoker.perform(req, resp);
	}
	
	private static final Logger log = LogManager.getLogger(Handler.class);

}
